We were tasked with the creation of a single player classic 2D game using the SFML 2.5+ game framework. The overall concept of the game is a 2D side-scrolling platform game, which consists of multiple platform levels, which are tied together with a hub world. My main role for this project was being a core programmer. I was responsible for organising application flow and transitions between screens (e.g. Main Menu -> Hub -> Platform), Menu Systems, Representation of Global Game State (shared between the different screens/modes), Pickups, and the Shop/Inventory system.
Player Inventory and Game Shop share some behaviour such as storing various items and both allow selection and traversal between the stored items. I've decided to create a Inventory_Base class in order to implement all common functionality and then inherit from this class and extend it in other classes.
The main functionality of Inventory class is to to efficiently add new items into container, traverse the items as well as removing them completely and sorting the rest of the items in the inventory. The full list of required features include:
- storing all shop items in the inventory
- giving access to a specified index
- removing specified index from the container
- adding new items to the inventory
- traversal between specified indexes in the container
The most important types of data processing include:
• Fast Insertion/Deletion of elements - elements will be added dynamically in run time so insertion and deletion of the items in the data structure has be very efficient/ Insertion and deletion usually will take place after user input (ie Use item, remove item, buy item etc). However there are some exceptions such us creating an initial inventory at the beginning of the game(ie Filling the shop inventory with procedurally generated items).
• Data structure needs to grow and shrink as the game progresses - elements will be added and removed during the game so data structures will has to dynamically grow and shrink. The size of inventory also will be a variable so for example at the beginning of the game the size of the inventory will be 20, but during the game the size has been changed and at the end of the game it will be just 10 elements.
• Access to a specific element - data structure must provide an easy access to all elements stored. In both shop and player inventory user will be able to traverse between elements and if he will decide to buy/use selected item (current index), he will use appropriate input to do so. Access will be needed during the whole game as player will be able to open his inventory and use any item in all stages of the game. Game shop however will be only available in a game shop screen available from hub world only.
The data structure itself will be setup at the beginning of the game. Its size and contents will be dependent on the chosen options before the game started (game difficulty, player wealth). Then it will be saved into Global Game State. Most of the changes to the structure will take place during the update and after input processing. However all elements of the structure will have their graphical representation and text to render. This means that it will be required to loop through the container and render all elements in render functions. Weapons
I have created the base class EnemyWeaponBase. From this class I have help to create inherited weapon type: BlitzWeaponTest. It is a base class for EnemyBlitzWeapon,PistolWeapon and AssaultRifleWeapon. Weapons and Bullets have a Boolean isPlayerBullet. It is used to check during the collision event who should take damage and should bullet be destroyed in collision with the collider. For example we do not want to destroy a bullet which has been created from the player's weapon in collision with the player.
I've considered vector and array to be a container for my items in Inventory. In order to decide which one will be more suitable for my system I had to compare the two.
Memory Layout of the Structure
Vector and Array are both contiguous arrays. They are also sequence containers which is what I need for my systems as I expect to access the data very often and the changes in the array will be also made frequently. However there is no need for searching through data structures. Sorting of the array will be done only after certain operations done on the inventory/game shop. For example if Player will use item which index in the container is 5, this item will be removed from the array and all elements from and after index 5 will have to change their positions by one (6th element will become the 5th, 7th will become the 6th etc). Array is a very efficient data structure where each element in an array occupies a single space. The array itself only uses its allocated space once it is initialised. After initialisation array does not need to change its size due to it being non-dynamic. Vector requires much more memory due to being dynamic in nature. A typical vector implementation grows by doubling its allocated space instead of incrementing it by one as it is done in array. Reallocating memory is also usually an expensive operation in vectors.
Structure Size
Vector are structures dynamic in nature. This means that the size of the vector will automatically change as the element will be added or removed. Arrays at the other hand are fixed in size, which means that once the array has been initialised with values and has been allocated a space in memory, it will not allow to change the size of the array unless it is a dynamic array. In our game there will be situations where resizing the array will be required. As in the case of using a vector that is not a problem as it can be resized at any moment to a needed size just by using resize function which vector provides, the resizing of the array however will be impossible. As an example here, there is a concrete situation where resizing will be a must: Player has some items that he bought from the shop in his inventory. Player left the game and started the new game. However the inventory is still saved in Global game state. Using vector we can just resize the vector to 0 slots and then eventually reserve some space in inventory before starting a new game. However it will be impossible to dynamically resize the size of the standard array.
Structure Traversal
The elements of array and vector are stored continuously, which means that they are contiguous sections of data without spaces between the data that array holds. To traverse the structure we can access elements not only through iterators, but also using offsets to regular pointers to elements. The pointer to an element of a structure may be passed to any function that expects a pointer to an element stored in the structure. For vectors used in our application there will be a mix of using for loop and range-based loops, however the most frequently used will be range-based loops to easy traverse through all vector elements. It will be very useful for collision detection and looping through all items in render functions.
Performance of Key Operations
In terms of performance the array is more efficient in basic operations than a vector, however there are some functions provided by API in vector to help increase the performance. For example reallocations of the memory are usually costly operations. To avoid unnecessary reallocations we can call reserve(n), where n will be the number of locations in memory we can reserve beforehand. In general however vector will be more expensive in use than array due its dynamic nature. Vector will allocate all its elements on the heap. In terms of Algorithmic Time Complexity for accessing the structure element vectors has the same complexity as arrays, that is O(1). It means that accessing any element takes always the same time regardless of previous conditions and element being accessed. However, accessing an element of the vector using operator[] will cause small overheads for bounds checking. There are more key operations for vector and here is the time complexity for them as well:
• Insertion of new elements - O(1)
• Erase of elements - O(1)
• Return current number of elements - O(1)
In general however I think that functions provided by vector and its dynamic nature can compensate the performance differences.
I've chose vector to be a container for the items in Inventory System mainly because of it's dynamic nature. The size and content of an inventory will be unknown at compile time and it won't be constant. The size of an Inventory will dynamically change during the game so vector seems to be a better choice.
In my design I had to have most of the systems derived fro the Inventory_Base in mind. Therefore, I've created a UML class diagram which shows the relations between the core classes.
All basic functionality such us: creation of inventory grid, loading items info, rendering and basic input is put inside the base class.
I created custom functions responsible for adding and removing items from the inventory. These are addItem() and removeItem(). The elements of the inventory vector can be accessed by calling the getItem() function which will return a refence to the item of given index.
In Player Inventory class, more specific functions have been implemented. These include modifications to the player's properties such as health or armor based on the items and introduced gold as a base resource for buing new items.
Player Inventory is rendered as a container with item slots formed into a grid. The grid is highly customizable and it will adjust it's height and width to the number of row and columns from the inventory.
The shop in my system consists from several parts. The main part of the shop is the GameShop class itself which is inherited from the Inventory_Base - the class in which I implemented basic operations on the inventory. Using this approach I can just inherit from it and create any class which will require/have inventory system functionality from Inventory_Base. The examples of the classes that are inherited from it are: GameShop and PlayerInventory.
Inventory_Base class could be also used to create other grid based systems like: skill trees, equipment systems etc. The graphical representation of the shop and the place where user can buy new items is GameShopScreen. It is a class which will provide input for game shop and it will alsobe responsible for rendering of all game shop Items. Player inventory will be visible on this screen and the user will be able to see changes in his inventory after buying the selected item. Info about selected item is displayed in a separate window. UI
I helped to create the HUD for the Player. The code for the HUD is placed inside the GameStateTest class so that it is also inherited by all the game levels. I used Text pointers to create instances of the text and to be able to easily clear the memory dynamically. The new pointers for each text (enemies left, coins, lives and ammo) are being created and destroyed each frame.
Global Game State is repsonsible for managing the gamplay elements that should be saved and reused across different levels. Health and inventory content are great examples of elemnts that should be saved and then be again available after level transitions. Additionally, a difficulty level is saved in this class and the it is used to set specific gameplay properties such as inventory maximum capacity, items quality etc.
On each difficulty level, player will start with different properties. This allows for a greater customization of a game and provides a way to define a new game based on player's preferences. on easy difficulty, player will has more slots in inventory and the shop will offer more items as well.
On hard difficulty, the player will start with 1x3 grid in game shop and 2x2 grid in our Player Inventory. These changes can be easily done in run-time, so the inventory can be axpaned or shrinked as needed.
Items can be stored and used in Game Shop and Player inventory. All basic behaviour has been implemented in Item_Base class. It provides implementation for random item stats generation and other common functionality. Random generation was one of the core features of the Item class. It allows for great variety in items that can drop in game world as wel as can be bought in Game Shop. Items from several different categories can be generated with random names, textures and stats. These informations are stored in batches in .txt files. If Item is randomly generated, all required information is randomly fetched from corresponding files. For example, a random health pickup will use random name from Item_Names_Health.txt and it will randomly choose the texture file from the Item_Textures_Health.txt.
Pickup Item is a class inherited from the Item_Base and provides functions that allow for direct interaction with Player. Pickup can modify Player properties such as health, armor etc.